library(tidyverse)
library(sf)
library(mapview)
Intro
This Rmarkdown notebook aims to show some examples of how to solve
the classroom proposed exercises in QGIS, but in R.
There are several ways to reach the same solution. Here we present
only one of them.
Represent Transport
Zones
Download and open TRIPSgeo_mun.gpkg and
TRIPSgeo_freg.gpkg under MQAT/geo/
repository.
TRIPSgeo_mun = st_read("geo/TRIPSgeo_mun.gpkg", quiet = TRUE) # we add quiet = TRUE so we don't get annoying messages on the info
TRIPSgeo_freg = st_read("geo/TRIPSgeo_freg.gpkg", quiet = TRUE)
# you can also open directly from url from github. example:
# TRIPSgeo_mun = st_read("https://github.com/U-Shift/MQAT/raw/main/geo/TRIPSgeo_mun.gpkg")
Represent Transport Zones with Total, and with Car %.
# create Car_per variable
TRIPSgeo_mun = TRIPSgeo_mun |> mutate(Car_per = Car / Total * 100)
TRIPSgeo_freg = TRIPSgeo_freg |> mutate(Car_per = Car / Total * 100)
#Vizualize in map
mapview(TRIPSgeo_mun, zcol = "Car_per")
mapview(TRIPSgeo_freg, zcol = "Car_per", col.regions = rev(hcl.colors(9, "-Inferno"))) #palete inferno com 9 classes, reverse color ramp
Centroids
Geometric
centroids
CENTROIDSgeo = st_centroid(TRIPSgeo_mun)
# mapview(CENTROIDSgeo)
Weigthed
centroids
Get BGRI Data from INE website, at Área Metropolitana
de Lisboa level: https://mapas.ine.pt/download/index2021.phtml
BGRI = st_read("original/BGRI21_LISBOA.gpkg", quiet = TRUE)
It is not that easy to estimate weighted centroids with R. See here.
We will make a bridge connection to QGIS to use its native function of
mean coordinates.
library(qgisprocess)
# qgis_search_algorithms("mean") # search the exact function name
# qgis_get_argument_specs("native:meancoordinates") |> select(name, description) # see the required inputs
# with population
CENTROIDSpop = qgis_run_algorithm(algorithm = "native:meancoordinates",
INPUT = BGRI,
WEIGHT = "N_INDIVIDUOS",
UID = "DTMN21")
CENTROIDSpop = st_as_sf(CENTROIDSpop)
# with buildings
CENTROIDSbuild = qgis_run_algorithm(algorithm = "native:meancoordinates",
INPUT = BGRI,
WEIGHT = "N_EDIFICIOS_CLASSICOS",
UID = "DTMN21")
CENTROIDSbuild = st_as_sf(CENTROIDSbuild)
Compare in map
mapview(CENTROIDSgeo) + mapview(CENTROIDSpop, col.region = "red") + mapview(CENTROIDSbuild, col.region = "black")
See how the building, poulation and geometric centroids of Montijo
are appart, from closer to Tagus, to the rural area.
Desire Lines
Download TRIPSdl_mun.gpkg
TRIPSdl_mun = st_read("geo/TRIPSdl_mun.gpkg", quiet = TRUE)
Filter intrazonal trips, and trips with origin or desination in
Lisbon.
TRIPSdl_mun = TRIPSdl_mun |>
filter(Origin_mun != Destination_mun) |>
filter(Total > 5000) # remove noise
mapview(TRIPSdl_mun, zcol = "Total", lwd = 5)
TRIPSdl_mun_noLX = TRIPSdl_mun |>
filter(Origin_mun != "Lisboa", Destination_mun != "Lisboa")
mapview(TRIPSdl_mun_noLX, zcol = "Total", lwd = 8)
You can replace the Total with other variable, such as
Car, PTransit, and so on.
Note: The function od_oneway()
aggregates oneway lines to produce bidirectional flows. By default, it
returns the sum of each numeric column for each bidirectional
origin-destination pair. This is better for viz purpouses.
Euclidean vs. Routing
distance
Euclidean
distance
Create new point at
IST
IST = st_sfc(st_point(c(-9.1397404, 38.7370168)), crs = 4326)
Import survey and
visualize
SURVEY = read.csv("geo/SURVEYist.txt", sep = "\t") # tab delimiter
SURVEY = st_as_sf(SURVEY, coords = c("lon", "lat"), crs = 4326) # transform as geo data
mapview(SURVEY, zcol = "MODE") + mapview(IST, col.region = "red", cex = 12)
Reproject
layers
In R we can process distances in meters on-fly.
Buy here is the code to project layers from Geographic coordinates
(WGS 84 - EPSG:4364) to Projected
coordinates (Pseudo-Mercator - EPSG:3857, or Portuguese Tranversor-Mercator
06 - EPSG:3763).
ISTprojected = st_transform(IST, crs = 3857)
SURVEYprojected = st_transform(SURVEY, crs = 3857)
Straight lines and
distance
Nearest point between the two layers. As we only have 1 point at IST
layer, we will have the same number of lines as number of surveys =
200.
SURVEYeuclidean = st_nearest_points(SURVEY, IST, pairwise = TRUE) |> st_as_sf() # this creates lines
mapview(SURVEYeuclidean)
SURVEY$distance = st_length(SURVEYeuclidean) # compute distance and add directly in the first layer
summary(SURVEY$distance) # in meters
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 298.1 1105.9 2185.5 2658.5 3683.4 8600.0
The same function can be used to find the closest GIRA station to
each survey home location. And also check where are the ones that are
far away from GIRA.
GIRA = st_read("geo/GIRA2023.geojson", quiet = TRUE) # we can also read geojson with this function!
nearest = st_nearest_feature(SURVEY, GIRA) # creates an index of the closest GIRA station id
SURVEY$distanceGIRA = st_distance(SURVEY, GIRA[nearest,], by_element = TRUE)
mapview(SURVEY, zcol = "distanceGIRA") +
mapview(GIRA, col.regions = "grey20", cex = 4, legend = FALSE)
Routing distance
Using an API key from OpenRouteService,
you should store it at your computer and never show it directly
on code.
For that usethis::edit_r_environ() and paste your token as
ORS_API_KEY="xxxxxxxxxxxxxxxxxxxxxx" (replace with your
token). Save the .Renviron file, and press Ctrl+Shift+F10
to restart R so it can take effect.
Work In Progress
LS0tCnRpdGxlOiAiR0lTIGluIFIgLSBleGVyY2lzZXMiCmF1dGhvcjogIlIgRsOpbGl4IgpkYXRlOiAiTVFBVCAyMDIzIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgIyBjb2RlX2ZvbGRpbmc6ICJoaWRlIgogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UpCmBgYAoKYGBge3IgbGlicmFyaWVzfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShzZikKbGlicmFyeShtYXB2aWV3KQpgYGAKCgojIEludHJvCgpUaGlzIFJtYXJrZG93biBub3RlYm9vayBhaW1zIHRvIHNob3cgc29tZSBleGFtcGxlcyBvZiBob3cgdG8gc29sdmUgdGhlIGNsYXNzcm9vbSBwcm9wb3NlZCBleGVyY2lzZXMgaW4gUUdJUywgYnV0IGluIFIuCgpUaGVyZSBhcmUgc2V2ZXJhbCB3YXlzIHRvIHJlYWNoIHRoZSBzYW1lIHNvbHV0aW9uLiBIZXJlIHdlIHByZXNlbnQgb25seSBvbmUgb2YgdGhlbS4KCiMgUmVwcmVzZW50IFRyYW5zcG9ydCBab25lcwoKRG93bmxvYWQgYW5kIG9wZW4gYFRSSVBTZ2VvX211bi5ncGtnYCBhbmQgYFRSSVBTZ2VvX2ZyZWcuZ3BrZ2AgdW5kZXIgW01RQVQvZ2VvL10oaHR0cHM6Ly9naXRodWIuY29tL1UtU2hpZnQvTVFBVC90cmVlL21haW4vZ2VvKSByZXBvc2l0b3J5LgoKYGBge3IgZ2V0ZGF0YTF9ClRSSVBTZ2VvX211biA9IHN0X3JlYWQoImdlby9UUklQU2dlb19tdW4uZ3BrZyIsIHF1aWV0ID0gVFJVRSkgIyB3ZSBhZGQgcXVpZXQgPSBUUlVFIHNvIHdlIGRvbid0IGdldCBhbm5veWluZyBtZXNzYWdlcyBvbiB0aGUgaW5mbwpUUklQU2dlb19mcmVnID0gc3RfcmVhZCgiZ2VvL1RSSVBTZ2VvX2ZyZWcuZ3BrZyIsIHF1aWV0ID0gVFJVRSkKCiMgeW91IGNhbiBhbHNvIG9wZW4gZGlyZWN0bHkgZnJvbSB1cmwgZnJvbSBnaXRodWIuIGV4YW1wbGU6CiMgVFJJUFNnZW9fbXVuID0gc3RfcmVhZCgiaHR0cHM6Ly9naXRodWIuY29tL1UtU2hpZnQvTVFBVC9yYXcvbWFpbi9nZW8vVFJJUFNnZW9fbXVuLmdwa2ciKQpgYGAKClJlcHJlc2VudCBUcmFuc3BvcnQgWm9uZXMgd2l0aCBUb3RhbCwgYW5kIHdpdGggQ2FyICUuCgpgYGB7cn0KIyBjcmVhdGUgQ2FyX3BlciB2YXJpYWJsZQpUUklQU2dlb19tdW4gPSBUUklQU2dlb19tdW4gfD4gbXV0YXRlKENhcl9wZXIgPSBDYXIgLyBUb3RhbCAqIDEwMCkKVFJJUFNnZW9fZnJlZyA9IFRSSVBTZ2VvX2ZyZWcgfD4gbXV0YXRlKENhcl9wZXIgPSBDYXIgLyBUb3RhbCAqIDEwMCkKCiNWaXp1YWxpemUgaW4gbWFwCm1hcHZpZXcoVFJJUFNnZW9fbXVuLCB6Y29sID0gIkNhcl9wZXIiKQptYXB2aWV3KFRSSVBTZ2VvX2ZyZWcsIHpjb2wgPSAiQ2FyX3BlciIsIGNvbC5yZWdpb25zID0gcmV2KGhjbC5jb2xvcnMoOSwgIi1JbmZlcm5vIikpKSAjcGFsZXRlIGluZmVybm8gY29tIDkgY2xhc3NlcywgcmV2ZXJzZSBjb2xvciByYW1wCmBgYAoKIyBDZW50cm9pZHMKCiMjIEdlb21ldHJpYyBjZW50cm9pZHMKCmBgYHtyfQpDRU5UUk9JRFNnZW8gPSBzdF9jZW50cm9pZChUUklQU2dlb19tdW4pCiMgbWFwdmlldyhDRU5UUk9JRFNnZW8pCmBgYAoKCiMjIFdlaWd0aGVkIGNlbnRyb2lkcwoKR2V0IEJHUkkgRGF0YV5bQmFzZSBHZW9ncsOhZmljYSBkZSBSZWZlcmVuY2lhw6fDo28gZGUgSW5mb3JtYcOnw6NvLCBDZW5zb3MgMjAyMV0gZnJvbSBJTkUgd2Vic2l0ZSwgYXQgKsOBcmVhIE1ldHJvcG9saXRhbmEgZGUgTGlzYm9hKiBsZXZlbDogW2h0dHBzOi8vbWFwYXMuaW5lLnB0L2Rvd25sb2FkL2luZGV4MjAyMS5waHRtbF0obWFwYXMuaW5lLnB0L2Rvd25sb2FkL2luZGV4MjAyMS5waHRtbCkKCmBgYHtyIGdldGNlbnN1c30KQkdSSSA9IHN0X3JlYWQoIm9yaWdpbmFsL0JHUkkyMV9MSVNCT0EuZ3BrZyIsIHF1aWV0ID0gVFJVRSkKYGBgCgpJdCBpcyBub3QgdGhhdCBlYXN5IHRvIGVzdGltYXRlIHdlaWdodGVkIGNlbnRyb2lkcyB3aXRoIFIuIFNlZSBbaGVyZV0oaHR0cHM6Ly93emJzb2NpYWxzY2llbmNlY2VudGVyLmdpdGh1Yi5pby9zcGF0aWFsbHlfd2VpZ2h0ZWRfYXZnLykuICAKV2Ugd2lsbCBtYWtlIGEgYnJpZGdlIGNvbm5lY3Rpb24gdG8gUUdJUyB0byB1c2UgaXRzIG5hdGl2ZSBmdW5jdGlvbiBvZiBtZWFuIGNvb3JkaW5hdGVzLiAgCgoKYGBge3J9CmxpYnJhcnkocWdpc3Byb2Nlc3MpCiMgcWdpc19zZWFyY2hfYWxnb3JpdGhtcygibWVhbiIpICMgc2VhcmNoIHRoZSBleGFjdCBmdW5jdGlvbiBuYW1lCiMgcWdpc19nZXRfYXJndW1lbnRfc3BlY3MoIm5hdGl2ZTptZWFuY29vcmRpbmF0ZXMiKSB8PiBzZWxlY3QobmFtZSwgZGVzY3JpcHRpb24pICMgc2VlIHRoZSByZXF1aXJlZCBpbnB1dHMKCiMgd2l0aCBwb3B1bGF0aW9uCkNFTlRST0lEU3BvcCA9IHFnaXNfcnVuX2FsZ29yaXRobShhbGdvcml0aG0gPSAibmF0aXZlOm1lYW5jb29yZGluYXRlcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJTlBVVCA9IEJHUkksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXRUlHSFQgPSAiTl9JTkRJVklEVU9TIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFVJRCA9ICJEVE1OMjEiKQpDRU5UUk9JRFNwb3AgPSBzdF9hc19zZihDRU5UUk9JRFNwb3ApCgojIHdpdGggYnVpbGRpbmdzCkNFTlRST0lEU2J1aWxkID0gcWdpc19ydW5fYWxnb3JpdGhtKGFsZ29yaXRobSA9ICJuYXRpdmU6bWVhbmNvb3JkaW5hdGVzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElOUFVUID0gQkdSSSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdFSUdIVCA9ICJOX0VESUZJQ0lPU19DTEFTU0lDT1MiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVUlEID0gIkRUTU4yMSIpCkNFTlRST0lEU2J1aWxkID0gc3RfYXNfc2YoQ0VOVFJPSURTYnVpbGQpCmBgYAoKCiMjIENvbXBhcmUgaW4gbWFwCgpgYGB7ciBtYXBjZW50cm9pZH0KbWFwdmlldyhDRU5UUk9JRFNnZW8pICsgbWFwdmlldyhDRU5UUk9JRFNwb3AsIGNvbC5yZWdpb24gPSAicmVkIikgKyBtYXB2aWV3KENFTlRST0lEU2J1aWxkLCBjb2wucmVnaW9uID0gImJsYWNrIikKYGBgCgpTZWUgaG93IHRoZSBidWlsZGluZywgcG91bGF0aW9uIGFuZCBnZW9tZXRyaWMgY2VudHJvaWRzIG9mIE1vbnRpam8gYXJlIGFwcGFydCwgZnJvbSBjbG9zZXIgdG8gVGFndXMsIHRvIHRoZSBydXJhbCBhcmVhLgoKCiMgRGVzaXJlIExpbmVzCgpEb3dubG9hZCBgVFJJUFNkbF9tdW4uZ3BrZ2AgCgpgYGB7ciBnZXRkbH0KVFJJUFNkbF9tdW4gPSBzdF9yZWFkKCJnZW8vVFJJUFNkbF9tdW4uZ3BrZyIsIHF1aWV0ID0gVFJVRSkgCmBgYAoKRmlsdGVyIGludHJhem9uYWwgdHJpcHMsIGFuZCB0cmlwcyB3aXRoIG9yaWdpbiBvciBkZXNpbmF0aW9uIGluIExpc2Jvbi4KCmBgYHtyIHdpdGhseH0KVFJJUFNkbF9tdW4gPSBUUklQU2RsX211biB8PiAKICBmaWx0ZXIoT3JpZ2luX211biAhPSBEZXN0aW5hdGlvbl9tdW4pIHw+IAogIGZpbHRlcihUb3RhbCA+IDUwMDApICMgcmVtb3ZlIG5vaXNlCgptYXB2aWV3KFRSSVBTZGxfbXVuLCB6Y29sID0gIlRvdGFsIiwgbHdkID0gNSkKYGBgCgpgYGB7ciBmaWx0ZXJseH0KVFJJUFNkbF9tdW5fbm9MWCA9IFRSSVBTZGxfbXVuIHw+IAogIGZpbHRlcihPcmlnaW5fbXVuICE9ICJMaXNib2EiLCBEZXN0aW5hdGlvbl9tdW4gIT0gIkxpc2JvYSIpCgptYXB2aWV3KFRSSVBTZGxfbXVuX25vTFgsIHpjb2wgPSAiVG90YWwiLCBsd2QgPSA4KQpgYGAKCllvdSBjYW4gcmVwbGFjZSB0aGUgYFRvdGFsYCB3aXRoIG90aGVyIHZhcmlhYmxlLCBzdWNoIGFzIGBDYXJgLCBgUFRyYW5zaXRgLCBhbmQgc28gb24uCgo+IE5vdGU6IFRoZSBmdW5jdGlvbiBbYG9kX29uZXdheSgpYF0oaHR0cHM6Ly9kb2NzLnJvcGVuc2NpLm9yZy9zdHBsYW5yL3JlZmVyZW5jZS9vZF9vbmV3YXkuaHRtbCkgYWdncmVnYXRlcyBvbmV3YXkgbGluZXMgdG8gcHJvZHVjZSBiaWRpcmVjdGlvbmFsIGZsb3dzLiBCeSBkZWZhdWx0LCBpdCByZXR1cm5zIHRoZSBzdW0gb2YgZWFjaCBudW1lcmljIGNvbHVtbiBmb3IgZWFjaCBiaWRpcmVjdGlvbmFsIG9yaWdpbi1kZXN0aW5hdGlvbiBwYWlyLiBUaGlzIGlzIGJldHRlciBmb3Igdml6IHB1cnBvdXNlcy4KCiMgRXVjbGlkZWFuIHZzLiBSb3V0aW5nIGRpc3RhbmNlCgojIyBFdWNsaWRlYW4gZGlzdGFuY2UKCiMjIyBDcmVhdGUgbmV3IHBvaW50IGF0IElTVAoKYGBge3IgY3JlYXRlaXN0fQpJU1QgPSBzdF9zZmMoc3RfcG9pbnQoYygtOS4xMzk3NDA0LCAzOC43MzcwMTY4KSksIGNycyA9IDQzMjYpCmBgYAoKIyMjIEltcG9ydCBzdXJ2ZXkgYW5kIHZpc3VhbGl6ZQoKYGBge3Igc3VydmV5fQpTVVJWRVkgPSByZWFkLmNzdigiZ2VvL1NVUlZFWWlzdC50eHQiLCBzZXAgPSAiXHQiKSAjIHRhYiBkZWxpbWl0ZXIKU1VSVkVZID0gc3RfYXNfc2YoU1VSVkVZLCBjb29yZHMgPSBjKCJsb24iLCAibGF0IiksIGNycyA9IDQzMjYpICMgdHJhbnNmb3JtIGFzIGdlbyBkYXRhCgptYXB2aWV3KFNVUlZFWSwgemNvbCA9ICJNT0RFIikgKyBtYXB2aWV3KElTVCwgY29sLnJlZ2lvbiA9ICJyZWQiLCBjZXggPSAxMikKYGBgCgojIyMgUmVwcm9qZWN0IGxheWVycwoKSW4gUiB3ZSBjYW4gcHJvY2VzcyBkaXN0YW5jZXMgaW4gbWV0ZXJzIG9uLWZseS4KCkJ1eSBoZXJlIGlzIHRoZSBjb2RlIHRvIHByb2plY3QgbGF5ZXJzIGZyb20gR2VvZ3JhcGhpYyBjb29yZGluYXRlcyAoV0dTIDg0IC0gRVBTRzpbNDM2NF0oaHR0cHM6Ly9lcHNnLmlvLzQzMjYpKSB0byBQcm9qZWN0ZWQgY29vcmRpbmF0ZXMgKFBzZXVkby1NZXJjYXRvciAtIEVQU0c6WzM4NTddKGh0dHBzOi8vZXBzZy5pby8zODU3KSwgb3IgUG9ydHVndWVzZSBUcmFudmVyc29yLU1lcmNhdG9yIDA2IC0gRVBTRzpbMzc2M10oaHR0cHM6Ly9lcHNnLmlvLzM3NjMpKS4KCmBgYHtyIHByb2plY3RsYXllcnN9CklTVHByb2plY3RlZCA9IHN0X3RyYW5zZm9ybShJU1QsIGNycyA9IDM4NTcpClNVUlZFWXByb2plY3RlZCA9IHN0X3RyYW5zZm9ybShTVVJWRVksIGNycyA9IDM4NTcpCmBgYAoKIyMjIFN0cmFpZ2h0IGxpbmVzIGFuZCBkaXN0YW5jZQoKTmVhcmVzdCBwb2ludCBiZXR3ZWVuIHRoZSB0d28gbGF5ZXJzLiBBcyB3ZSBvbmx5IGhhdmUgMSBwb2ludCBhdCBJU1QgbGF5ZXIsIHdlIHdpbGwgaGF2ZSB0aGUgc2FtZSBudW1iZXIgb2YgbGluZXMgYXMgbnVtYmVyIG9mIHN1cnZleXMgPSBgciBucm93KFNVUlZFWSlgLgoKYGBge3IgZXVjZGlzdGFuY2V9ClNVUlZFWWV1Y2xpZGVhbiA9IHN0X25lYXJlc3RfcG9pbnRzKFNVUlZFWSwgSVNULCBwYWlyd2lzZSA9IFRSVUUpIHw+IHN0X2FzX3NmKCkgIyB0aGlzIGNyZWF0ZXMgbGluZXMKCm1hcHZpZXcoU1VSVkVZZXVjbGlkZWFuKQoKU1VSVkVZJGRpc3RhbmNlID0gc3RfbGVuZ3RoKFNVUlZFWWV1Y2xpZGVhbikgIyBjb21wdXRlIGRpc3RhbmNlIGFuZCBhZGQgZGlyZWN0bHkgaW4gdGhlIGZpcnN0IGxheWVyCgpzdW1tYXJ5KFNVUlZFWSRkaXN0YW5jZSkgIyBpbiBtZXRlcnMKYGBgCgpUaGUgc2FtZSBmdW5jdGlvbiBjYW4gYmUgdXNlZCB0byBmaW5kIHRoZSBjbG9zZXN0IEdJUkEgc3RhdGlvbiB0byBlYWNoIHN1cnZleSBob21lIGxvY2F0aW9uLiBBbmQgYWxzbyBjaGVjayB3aGVyZSBhcmUgdGhlIG9uZXMgdGhhdCBhcmUgZmFyIGF3YXkgZnJvbSBHSVJBLgoKYGBge3J9CkdJUkEgPSBzdF9yZWFkKCJnZW8vR0lSQTIwMjMuZ2VvanNvbiIsIHF1aWV0ID0gVFJVRSkgIyB3ZSBjYW4gYWxzbyByZWFkIGdlb2pzb24gd2l0aCB0aGlzIGZ1bmN0aW9uIQoKbmVhcmVzdCA9IHN0X25lYXJlc3RfZmVhdHVyZShTVVJWRVksIEdJUkEpICMgY3JlYXRlcyBhbiBpbmRleCBvZiB0aGUgY2xvc2VzdCBHSVJBIHN0YXRpb24gaWQKClNVUlZFWSRkaXN0YW5jZUdJUkEgPSBzdF9kaXN0YW5jZShTVVJWRVksIEdJUkFbbmVhcmVzdCxdLCBieV9lbGVtZW50ID0gVFJVRSkKCm1hcHZpZXcoU1VSVkVZLCB6Y29sID0gImRpc3RhbmNlR0lSQSIpICsKICBtYXB2aWV3KEdJUkEsIGNvbC5yZWdpb25zID0gImdyZXkyMCIsIGNleCA9IDQsIGxlZ2VuZCA9IEZBTFNFKQpgYGAKCiMjIFJvdXRpbmcgZGlzdGFuY2UKClVzaW5nIGFuIEFQSSBrZXkgZnJvbSBbT3BlblJvdXRlU2VydmljZV0oaHR0cHM6Ly9vcGVucm91dGVzZXJ2aWNlLm9yZy9kZXYvIy9zaWdudXApLCB5b3Ugc2hvdWxkIHN0b3JlIGl0IGF0IHlvdXIgY29tcHV0ZXIgYW5kICoqbmV2ZXIgc2hvdyBpdCBkaXJlY3RseSBvbiBjb2RlKiouICAKRm9yIHRoYXQgYHVzZXRoaXM6OmVkaXRfcl9lbnZpcm9uKClgIGFuZCBwYXN0ZSB5b3VyIHRva2VuIGFzIGBPUlNfQVBJX0tFWT0ieHh4eHh4eHh4eHh4eHh4eHh4eHh4eCJgIChyZXBsYWNlIHdpdGggeW91ciB0b2tlbikuIFNhdmUgdGhlIC5SZW52aXJvbiBmaWxlLCBhbmQgcHJlc3MgYEN0cmwrU2hpZnQrRjEwYCB0byByZXN0YXJ0IFIgc28gaXQgY2FuIHRha2UgZWZmZWN0LgoKPCEtLSBVc2UgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJHSVNjaWVuY2Uvb3BlbnJvdXRlc2VydmljZS1yIikgLS0+CgoqV29yayBJbiBQcm9ncmVzcyo=